<template>
  <div :id="wrapperId" class="vui-inputSearch">
    <div class="flex items-center vui-inputSearch-input">
      <span class="block w-4">
        <svg
          class="icon mr-1"
          fill="#bfbfbf"
          height="14"
          p-id="2109"
          t="1657674533152"
          version="1.1"
          viewBox="0 0 1024 1024"
          width="14"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            d="M953.474215 908.234504l-152.576516-163.241391c61.92508-74.48211 95.81186-167.36973 95.81186-265.073744 0-229.294809-186.63531-415.930119-416.102133-415.930119-229.294809 0-415.930119 186.63531-415.930119 415.930119s186.63531 415.930119 415.930119 415.930119c60.032925 0 118.00168-12.55703 172.186125-37.327062 16.169326-7.396607 23.221905-26.318159 15.825298-42.315471-7.396607-16.169326-26.318159-23.221905-42.315471-15.825298-45.927768 20.813707-94.951789 31.478582-145.695952 31.478582-194.031917 0-351.94087-157.908953-351.94087-351.94087 0-194.031917 157.908953-351.94087 351.94087-351.94087 194.031917 0 351.94087 157.908953 351.94087 351.94087 0 91.339493-34.918864 177.86259-98.048043 243.743995-12.213002 12.729044-11.868974 33.026709 0.860071 45.239711 1.032085 0.860071 2.236183 1.204099 3.268268 2.064169 0.860071 1.204099 1.376113 2.752226 2.408198 3.956325l165.477574 177.00252c6.192508 6.70855 14.793214 10.148833 23.393919 10.148833 7.912649 0 15.653284-2.92424 21.845792-8.600706C964.827146 941.433227 965.515202 921.135562 953.474215 908.234504z"
            p-id="2110"
          />
        </svg>
      </span>
      <!-- 此input用来掩盖,防止浏览器弹出"自动填充密码" -->
      <input name="txtPassword" style="display: none" type="password" />
      <input
        :id="inputId"
        v-model="input"
        autocomplete="off"
        :placeholder="placeholder"
        type="text"
        v-bind="$attrs"
        @blur="onBlur"
        @focus="onFocus"
        @input="onInput"
        @keydown.down.prevent="onArrowDown"
        @keydown.enter.tab.prevent="selectCurrentSelection"
        @keydown.up.prevent="onArrowUp"
      />
      <div class="w-4">
        <a v-if="input" class="cursor-pointer" @click="clearInput">
          <svg
            class="icon"
            height="14"
            p-id="3290"
            t="1657675392490"
            version="1.1"
            viewBox="0 0 1024 1024"
            width="14"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M512 1024C229.248 1024 0 794.752 0 512S229.248 0 512 0s512 229.248 512 512-229.248 512-512 512z m0-572.330667L300.629333 240.213333a42.538667 42.538667 0 0 0-60.16 0.213334 42.410667 42.410667 0 0 0-0.213333 60.16L451.669333 512 240.213333 723.370667a42.538667 42.538667 0 0 0 0.213334 60.16 42.410667 42.410667 0 0 0 60.16 0.213333L512 572.330667l211.370667 211.413333a42.538667 42.538667 0 0 0 60.16-0.213333 42.410667 42.410667 0 0 0 0.213333-60.16L572.330667 512l211.413333-211.370667a42.538667 42.538667 0 0 0-0.213333-60.16 42.410667 42.410667 0 0 0-60.16-0.213333L512 451.669333z"
              fill="#bfbfbf"
              p-id="3291"
            />
          </svg>
        </a>
      </div>
    </div>
    <div v-if="isListVisible" class="vui-inputSearch-list">
      <div v-if="$slots['list-header']" class="vui-inputSearch-list-header"><slot name="list-header"></slot></div>
      <div
        v-for="(item, index) in filteredItems"
        :key="index"
        class="vui-inputSearch-list-item"
        :class="{ 'vui-inputSearch-list-item-active': currentSelectionIndex == index }"
        @click="selectItem(item)"
        @mousedown.prevent
        @mouseenter="currentSelectionIndex = index"
      >
        <span v-if="$slots['list-item-text']" class="vui-inputSearch-list-item-text" :data-text="itemProjection(item)">
          <slot :boldMatchText="boldMatchText" :item="item" :itemProjection="itemProjection" name="list-item-text"></slot>
        </span>
        <span v-else class="vui-inputSearch-list-item-text" :data-text="itemProjection(item)" v-html="boldMatchText(itemProjection(item))"></span>
      </div>
      <div v-if="$slots['list-footer']" class="vui-inputSearch-list-footer"><slot name="list-footer"></slot></div>
    </div>
  </div>
</template>

<script>
  import { defineComponent } from 'vue'

  export default /*#__PURE__*/ defineComponent({
    name: 'VuiInputSearch',
    props: {
      id: {
        type: String,
        default: '',
      },
      placeholder: {
        type: String,
        default: '',
      },
      items: {
        type: Array,
        required: true,
      },
      defaultItem: {
        type: String,
        default: null,
      },
      itemProjection: {
        type: Function,
        default(item) {
          return item
        },
      },
      minInputLength: {
        type: Number,
        default: 2,
        validator: (prop) => {
          return prop >= 0
        },
      },
    },
    emits: ['onInput', 'onFocus', 'onBlur', 'selectItem'],
    data() {
      return {
        inputId: this.id || `simple_typeahead_${(Math.random() * 1000).toFixed()}`,
        input: '',
        isInputFocused: false,
        currentSelectionIndex: 0,
      }
    },
    computed: {
      wrapperId() {
        return `${this.inputId}_wrapper`
      },
      filteredItems() {
        const regexp = new RegExp(this.escapeRegExp(this.input), 'i')
        return this.items.filter((item) => this.itemProjection(item.value).match(regexp))
      },
      isListVisible() {
        return this.isInputFocused && this.input.length >= this.minInputLength && this.filteredItems.length
      },
      currentSelection() {
        return this.isListVisible && this.currentSelectionIndex < this.filteredItems.length
          ? this.filteredItems[this.currentSelectionIndex]
          : undefined
      },
    },
    mounted() {
      if (this.defaultItem !== undefined && this.defaultItem !== null) {
        this.selectItem(this.defaultItem)
      }
    },
    methods: {
      clearInput() {
        this.input = ''
      },
      onInput() {
        if (this.isListVisible && this.currentSelectionIndex >= this.filteredItems.length) {
          this.currentSelectionIndex = (this.filteredItems.length || 1) - 1
        }
        this.$emit('onInput', { input: this.input, items: this.filteredItems })
      },
      onFocus() {
        this.isInputFocused = true
        this.$emit('onFocus', { input: this.input, items: this.filteredItems })
      },
      onBlur() {
        this.isInputFocused = false
        this.$emit('onBlur', { input: this.input, items: this.filteredItems })
      },
      onArrowDown() {
        if (this.isListVisible && this.currentSelectionIndex < this.filteredItems.length - 1) {
          this.currentSelectionIndex++
        }
        this.scrollSelectionIntoView()
      },
      onArrowUp() {
        if (this.isListVisible && this.currentSelectionIndex > 0) {
          this.currentSelectionIndex--
        }
        this.scrollSelectionIntoView()
      },
      scrollSelectionIntoView() {
        setTimeout(() => {
          const list_node = document.querySelector(`#${this.wrapperId} .vui-inputSearch-list`)
          const active_node = document.querySelector(`#${this.wrapperId} .vui-inputSearch-list-item.vui-inputSearch-list-item-active`)

          if (
            !(
              active_node.offsetTop >= list_node.scrollTop &&
              active_node.offsetTop + active_node.offsetHeight < list_node.scrollTop + list_node.offsetHeight
            )
          ) {
            let scroll_to = 0
            if (active_node.offsetTop > list_node.scrollTop) {
              scroll_to = active_node.offsetTop + active_node.offsetHeight - list_node.offsetHeight
            } else if (active_node.offsetTop < list_node.scrollTop) {
              scroll_to = active_node.offsetTop
            }

            list_node.scrollTo(0, scroll_to)
          }
        })
      },
      selectCurrentSelection() {
        if (this.currentSelection) {
          this.selectItem(this.currentSelection)
        }
      },
      selectItem(item) {
        this.input = this.itemProjection(item.value)
        this.currentSelectionIndex = 0
        document.getElementById(this.inputId).blur()
        this.$emit('selectItem', item)
      },
      escapeRegExp(string) {
        return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
      },
      boldMatchText(text) {
        const regexp = new RegExp(`(${this.escapeRegExp(this.input)})`, 'ig')
        return text.value.replace(regexp, '<strong>$1</strong>')
      },
    },
  })
</script>

<style scoped>
  .vui-inputSearch {
    position: relative;
    width: 100%;
  }
  .vui-inputSearch .vui-inputSearch-input {
    margin-bottom: 0;
    background: #4f5c78;
    border-radius: 4px;
    padding: 2px 0;
    width: 100%;
    font-size: 12px;
    color: #fff;
  }
  .vui-inputSearch .vui-inputSearch-input:hover,
  .vui-inputSearch .vui-inputSearch-input:active {
    background-color: #6b7894;
  }
  .vui-inputSearch .vui-inputSearch-input input {
    background-color: transparent;
    padding: 4px 0;
  }
  .vui-inputSearch .vui-inputSearch-list {
    position: absolute;
    width: 100%;
    border: none;
    max-height: 400px;
    overflow-y: auto;
    z-index: 9;
    border-radius: 2px;
  }

  .vui-inputSearch .vui-inputSearch-list .vui-inputSearch-list-header {
    background-color: #fafafa;
    padding: 0.6rem 1rem;
    border-bottom: 0.1rem solid #d1d1d1;
    border-left: 0.1rem solid #d1d1d1;
    border-right: 0.1rem solid #d1d1d1;
  }
  .vui-inputSearch .vui-inputSearch-list .vui-inputSearch-list-footer {
    background-color: #fafafa;
    padding: 0.6rem 1rem;
    border-left: 0.1rem solid #d1d1d1;
    border-right: 0.1rem solid #d1d1d1;
  }
  .vui-inputSearch .vui-inputSearch-list .vui-inputSearch-list-item {
    cursor: pointer;
    background-color: #7c89a5;
    padding: 8px 4px;
    font-size: 12px;
    color: #fff;
  }

  .vui-inputSearch .vui-inputSearch-list .vui-inputSearch-list-item:last-child {
    border-bottom: none;
  }

  .vui-inputSearch .vui-inputSearch-list .vui-inputSearch-list-item.vui-inputSearch-list-item-active {
    background-color: #4f5c78;
  }

  input:-webkit-autofill {
    -webkit-box-shadow: 0 0 0px 100px #4f5c78 inset;
    box-shadow: 0 0 0px 100px #4f5c78 inset;
  }
  input:hover:-webkit-autofill {
    -webkit-box-shadow: 0 0 0px 100px #6b7894 inset;
    box-shadow: 0 0 0px 100px #6b7894 inset;
  }
</style>
